package cn.tedu.tmall.admin.mall.service.impl;

import cn.tedu.tmall.admin.mall.dao.persist.repository.ICategoryCacheRepository;
import cn.tedu.tmall.admin.mall.dao.persist.repository.ICategoryRepository;
import cn.tedu.tmall.admin.mall.pojo.param.CategoryAddParam;
import cn.tedu.tmall.admin.mall.pojo.po.CategoryPO;
import cn.tedu.tmall.admin.mall.pojo.vo.CategoryTreeVO;
import cn.tedu.tmall.admin.mall.service.ICategoryService;
import cn.tedu.tmall.common.enumerator.ServiceCode;
import cn.tedu.tmall.common.ex.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 *     //todo  1 删除要考虑删除数据库要同步删除redis
 *         //  2 更新要考虑更新数据库要同步更新redis
 *         //  3 新增要考虑新增数据库要同步新增redis
 *         // 保持数据库和缓存一致
 */
@Slf4j
@Service
@Transactional
public class CategoryServiceImpl implements ICategoryService {


    @Autowired
    private ICategoryRepository categoryRepository;

    @Autowired
    private ICategoryCacheRepository categoryCacheRepository;


    /**
     * //业务代码
     *         //1 类别名称必须唯一
     *         //  如果不唯一 报错
     *         //  如果唯一 继续
     *         //2 要判断插入的类别是子类别还是父类别
     *         //    子类别
     *         //      看他的父类列是否存在
     *         //          存在 继续
     *         //          不存在 报错
     *         //    父类别
     *         //       直接保存
     *         // 3 计算深度
     *         //    如果是父类或者parentId=0 深度为1
     *         //    如果是子类他的深度是他得父类的深度加 1 ;
     *         // 4 设置isParent
     *         //    如果是父类,不设置,因为你还没有子类,你不能设置isParent为1
     *         //    如果是子类,把他的父类的isParent设置为1,
     *               并且为了避免重复更新isParent这个字段,我们加个判断,isParent 等于0 的时候更新
     *               等于1 的时候不更新
     *
     * @param categoryAddParam CategoryAddParam
     */
    @Override
    @Transactional
    public void addCategory(CategoryAddParam categoryAddParam) {
        log.debug("增加类别 入参:{}",categoryAddParam);
        String name = categoryAddParam.getName();
        Integer count = categoryRepository.countCategoryByName(name);
        if (count > 0) {
            String message = "类别已经存在,增加失败";
            log.error(message);
            throw new ServiceException(ServiceCode.ERROR_BAD_REQUEST,message);
        }
        Long parentId = categoryAddParam.getParentId();
        Integer depth = 1;
        log.debug("增加类别 parentId:{}",parentId);
        if (parentId == 0) { //魔数  0 ,1 -1 99 等等是没有业务含义的,更建议定义成常量
            log.debug("增加类别 parentId:{},为父类别,深度:{}",parentId,depth);
            //保存类别
            //如果是父类别其实是顶层 深度为1 ;
            CategoryPO categoryPO = saveCategory(categoryAddParam, depth);
            //往redis放一个
            log.debug("增加父类别到redis里:{}",categoryPO);
            categoryCacheRepository.saveCategory(categoryPO);
            log.debug("增加类别 parentId:{},为父类别,保存成功",parentId);
        }else {
            CategoryPO categoryPO;
            log.debug("增加类别 parentId:{},为子类别,深度:{}",parentId,depth);
            CategoryPO parentCategory = categoryRepository.getCategoryById(parentId);
            if (parentCategory != null) {
                //保存类别
                Integer parentCategoryDepth = parentCategory.getDepth();
                depth = parentCategoryDepth + 1;
                //如果类别为子类别,深度是它父类别的深度 + 1 ;
                categoryPO = saveCategory(categoryAddParam, depth);//1 一次更新
                log.debug("增加类别 parentId:{},为子类别,保存成功",parentId);
            } else {
                String message = "保存类别失败,父类别不存在.";
                log.error(message);
                throw new ServiceException(ServiceCode.ERROR_BAD_REQUEST,message);
            }
            //如何计算isParent
            // AA 如果我们新增的是子类,在保存子类的时候,要把他的父类的isParent 设置 1
            // BB 如果我们新增的是父类,我们不需要设置.
            // 当他有了子类以后 要增加子类(是不是就走了上面逻辑 AA ),他才能叫做父类.isParent 设置 1
            //if (parentId != 0) { //可优化,如果再一次插入当前类的子类,还是会走下面的更新逻辑,又多更新了一遍,
                //多操作了一次数据库.

            //突然出问题了,数据库磁盘坏了,网出问题了,
            //故意抛出一个异常  NPE
            //"".substring(10);


            if (parentCategory.getIsParent() == 0) {//已经被更新过了,parent!=0,我不更新了
                //parentId 不等于0 ,当前就是子类
                CategoryPO update = new CategoryPO();
                //设置父类的id
                update.setId(parentId);
                update.setIsParent(1);
                Integer updateRows = categoryRepository.updateById(update);//2 一次更新
                if (updateRows != 1 ){
                    String message = "保存类别失败,请重试.";
                    log.error(message);
                    throw new ServiceException(ServiceCode.ERROR_UNKNOWN,message);
                }
            }
            //增加子类，到父类的list
            log.debug("增加子类别到redis里:{}",categoryPO);
            categoryCacheRepository.saveCategoryByParentId(parentId,categoryPO);
        }
        //todo
        // 1 会导致类别变化的功能 都需要去更新redis 目的是 保持实时数据同步
        // 2  每那么实时，我们写个定时任务 每天晚上业务不忙的时候 重写同步下数据 业务表现上白天的修改，
        // 在晚上会进行同步，同步后用户就能看到我们最新的类别了

        //每次增加类别后，都去全部查询，然后更新
        //结论是可以实现，但是不太好，我们应该修改哪个更新哪个
        // todo 实时方案 自己去研究 如果实现管理员修改哪个类别，你去更新哪个类别 实时更新方案
        //   要调整redis的数据结构，不能用简单的k-v 用什么？
        //  我们增加了一个类别，数据库增加后 去redis里增加就好
        // todo 延迟更新方案 参考 SyncMallCategoryTask
        //initCategoryToCache();
        //新增子类和新增父类的不同处理

    }

    /**
     * 表示最顶级的类别id
     */
    private final Long TOP_CATEGORY_ID = 0L;

    /**
     * // 2 List<CategoryPO> 转换为   List<CategoryTreeVO>
     *         //   2.1 能直接转换么?
     *         //    2.1.1 怎么转换 ?
     *         //      遍历 List<CategoryPO>
     *         //      BeanUtils.copyProperties();  怎么实现的? 反射
     *         //        name 和 id 可以直接 copy 为什么?
     *         //        children 怎么计算?
     *         //          谁有children 父类才有
     *         //          1 List<CategoryTreeVO> 里面放的都是顶级的分类 ,什么是顶级,没有父类的类别,ParentId = 0
     *         //          2 取顶级分类的子类,然后放到顶级分类的 children
     *         //          3 取顶级分类的子类的子类 然后放到顶级分类的子类 children
     *         //          4 取顶级分类的子类的子类的子类 然后放到顶级分类的子类的子类 children
     *         //          5 ...
     *         //   我们不知道当前类别有多少层, 我们可以考虑递归 ,自己调自己 退出条件,如果没有,死循环 ,如果层级是固定的可以考虑不使用递归
     * @return List<CategoryTreeVO> 分类树的VO列表
     */
    @Override
    public List<CategoryTreeVO> treeCategory() {
        //梳理核心业务逻辑
        //1 查询所有数据
        //2 取top-po转为top-vo
        //3 设置top-vo的子类别
        // 自顶向下编程

        //定义返回结果的list列表
        List<CategoryTreeVO> result = new ArrayList<>();

        //1 查询所有数据
        List<CategoryPO> categoryPOS = getAllCategory();

        //2 取top-po转为top-vo
        List<CategoryTreeVO> topCategoryVo = getTopCategoryVo(categoryPOS);
        //设置top-vo的子类别
        //循环所有的top
        for (CategoryTreeVO categoryTreeVO : topCategoryVo) {
            //设置categoryTreeVO的子类别,子类别从哪来? categoryPOS
            buildChild(categoryTreeVO,categoryPOS);
            //4 设置到结果集
            result.add(categoryTreeVO);
        }
        return result;
    }

    /**
     *
     *  从数据库获取
     */
    @Override
    public List<CategoryTreeVO> topTreeCategory() {
        //要直接从数据库查询我们想要的数据，parentId = 0 ；
        //不能获取所有的从数据库查询所有数据，然后再遍历，过滤出parentId = 0 ； 没有意义了
        //我们的目的是减少数据库的查询的数据
        //我们需要什么就查什么，不能全部查询出来，然后再程序里面过滤。
        List<CategoryPO> dbCategoryPOS = categoryRepository.getTopCategory();
        return categoryListPO2VO(dbCategoryPOS);

    }

    @Override
    public List<CategoryTreeVO> childrenTreeCategory(Long id) {
        List<CategoryPO> categoryPOS = categoryRepository.getChildrenById(id);
        return categoryListPO2VO(categoryPOS);
    }

    @Override
    public List<CategoryTreeVO> treeCategoryFromCache() {
        List<CategoryTreeVO> categoryTreeVOS = categoryCacheRepository.listAllCategory();
        log.info("从redis获取商品分类数据:{}",categoryTreeVOS);
        return categoryTreeVOS;
    }

    /**
     *  从数据库查询
     *  放到redis里
     */
    @Override
    public void initCategoryToCache() {
        //1 先从数据库查询
        List<CategoryTreeVO> categoryTreeVOS = treeCategory();
        log.info("从数据库加载商品类别数据:{}",categoryTreeVOS);
        //2 保存到redis
        //我们的这种结构用redis什么的数据结构来存？
        //1 我们能不能直接把这个list  categoryTreeVOS 存到redis里
        //这里不能直接使用redisTemplate 我的业务要和具体的数据源隔离开，
        // 委托给Repository来实现。
        //todo 我们调用现有的方法treeCategory，来查询商品类别树返回的是VO
        //todo categoryCacheRepository 操作的PO ，我们偷懒了，让categoryCacheRepository 帮我们保存了VO
        //todo 推荐categoryCacheRepository 操作的PO ，交给你们
        //todo 我的类别数据预热成功了
        // 后来 我们的管理员 修改一个类别 ，比如 某个类别名字改了，或者删除一个类别，更新了数据库。
        // 并没有同步更新redis，我的商城用户查询的是预热时候的数据。
        categoryCacheRepository.saveCategory(categoryTreeVOS);
    }

    /**
     * 增加redis
     * 读写模型 先从redis读，如果redis不存在，从数据库读，读完以后更新到redis
     * 下次再读的时候，redis有直接返回。
     * @param
     * @return
     */
    @Override
    public List<CategoryTreeVO> childrenTreeCacheCategory(Long parentId) {
        List<CategoryPO> cacheCategoryPOS = categoryCacheRepository.getChildrenById(parentId);
        log.debug("从redis获取子类别:{},父类别id:{}",cacheCategoryPOS,parentId);
        if (!CollectionUtils.isEmpty(cacheCategoryPOS)) {
            log.debug("从redis获取子类别不为空");
            return categoryListPO2VO(cacheCategoryPOS);
        }else {
            log.debug("从redis获取子类别数据为空");
            List<CategoryPO> dbCategoryPOS = categoryRepository.getChildrenById(parentId);
            log.debug("从数据库获取子类别:{}，父类别id:{}",dbCategoryPOS,parentId);
            if (!CollectionUtils.isEmpty(dbCategoryPOS)) {
                log.debug("从数据库获取顶级类别数据不为空，更新到redis");
                categoryCacheRepository.saveCategoryPO(parentId,dbCategoryPOS);
                return categoryListPO2VO(dbCategoryPOS);
            }
        }
        return Collections.emptyList(); //new ArrayList<>();
    }

    /**
     * 增加redis
     * 读写模型 先从redis读，如果redis不存在，从数据库读，读完以后更新到redis
     * 下次再读的时候，redis有直接返回。
     * @return
     */
    @Override
    public List<CategoryTreeVO> topTreeCacheCategory() {
        List<CategoryPO> cacheCategoryPOS = categoryCacheRepository.getTopCategory();
        log.debug("从redis获取顶级类别:{}",cacheCategoryPOS);
        if (!CollectionUtils.isEmpty(cacheCategoryPOS)) {
            log.debug("从redis获取顶级类别不为空");
            return categoryListPO2VO(cacheCategoryPOS);
        }else {
            log.debug("从redis获取顶级类别数据为空");
            List<CategoryPO> dbCategoryPOS = categoryRepository.getTopCategory();
            log.debug("从数据库获取顶级类别:{}",dbCategoryPOS);
            if (!CollectionUtils.isEmpty(dbCategoryPOS)) {
                log.debug("从数据库获取顶级类别数据不为空，更新到redis");
                categoryCacheRepository.saveCategoryPO(dbCategoryPOS);
                return categoryListPO2VO(dbCategoryPOS);
            }
        }
        return Collections.emptyList();
    }


    /**
     * 设置类别的子类别
     * 从所有的类别中找到当前的顶级类别的子类别
     * 递归 buildChild 调用了 buildChild
     * 退出条件
     * @param categoryTreeVO 当前的顶级类别
     * @param categoryPOS 所有的类别
     *
     *
     */
    private CategoryTreeVO buildChild(CategoryTreeVO categoryTreeVO, List<CategoryPO> categoryPOS){
        List<CategoryTreeVO> children = new ArrayList<>();
        for (CategoryPO categoryPO : categoryPOS) {
            //取子类别
            if (categoryTreeVO.getId().equals(categoryPO.getParentId())) {
                //获取到了子类,子类不是一个,应该是个列表,
                //第一次循环获取的时候顶层的子类别
                CategoryTreeVO childVo = categoryPO2VO(categoryPO);
                //children.add(childVo) ,但是顶级类别的子类别的子类别找不到了.
                children.add(buildChild(childVo,categoryPOS));
            }else {
                //这个时候不会调用buildChild了,就算退出了, 不调了就算退出.
                // 所以else 的if categoryTreeVO.getId().equals(categoryPO.getParentId())
                //退出条件
            }
        }
        categoryTreeVO.setChildren(children);
        return categoryTreeVO;
    }



    /**
     * 通过所有的CategoryPO获取顶级的CategoryPO转换为CategoryTreeVO列表
     * @param categoryPOS
     * @return
     */
    private List<CategoryTreeVO> getTopCategoryVo(List<CategoryPO> categoryPOS){
        //定义返回结果的list列表
        List<CategoryTreeVO> result = new ArrayList<>();
        for (CategoryPO categoryPO : categoryPOS) {
            //获取parentId
            Long parentId = categoryPO.getParentId();
            //0 魔数 定义常量或者枚举
            // 判断类别是否是最顶级的类别,什么是最顶级,没有父类的类别 ParentId == 0
            if (TOP_CATEGORY_ID.equals(parentId)) {
                log.debug("当前分类为最顶级的分类:{}",categoryPO);
                //是最顶级
                //top-po转换为top-vo
                CategoryTreeVO topVO = categoryPO2VO(categoryPO);
                //添加top到返回结果的list列表
                result.add(topVO);
            }
        }
        return  result;
    }

    /**
     * 1 获取所有数据
     */
    private List<CategoryPO> getAllCategory(){
        //1 从数据库查询所有的分类 全部加载
        // categoryRepository.listAll(); 2K 20K 200K ,分页
        // OOM OutMemoryError jvm 堆大小20K 对象, 200K
        return categoryRepository.listAll();
    }


    /**
     * 去top的列表里通过遍历查找top的children,
     *  如果存在,返回
     *  不存在  返回null
     * @param id Long
     * @param result List<CategoryTreeVO>
     * @return
     * 定义出了核心问题,
     * todo 方法名定义待商榷
     */
    private CategoryTreeVO getVOById(Long id, List<CategoryTreeVO> result){
        for (CategoryTreeVO categoryTreeVO : result) {
            List<CategoryTreeVO> children = categoryTreeVO.getChildren();
            for (CategoryTreeVO child : children) {
                if (id.equals(child.getId())){
                    return child;
                }
                List<CategoryTreeVO> children1 = child.getChildren();
                if (!CollectionUtils.isEmpty(children1)) {
                    for (CategoryTreeVO vo : children1) {
                        List<CategoryTreeVO> children2 = vo.getChildren();
                        if (id.equals(vo.getId())){
                            return child;
                        }
                    }
                }
            }
        }
        return null;
    }

    /**
     * 把CategoryPO转换为 CategoryTreeVO
     * 没有用BeanUtils.copy
     * @param categoryPO CategoryPO
     * @return CategoryTreeVO
     */
    private CategoryTreeVO categoryPO2VO(CategoryPO categoryPO) {
        CategoryTreeVO vo = new CategoryTreeVO();
        vo.setId(categoryPO.getId());
        vo.setName(categoryPO.getName());
        return vo;
    }

    /**
     * 把CategoryPO列表转换为 CategoryTreeVO列表
     * @param categoryPO List<CategoryPO>
     * @return List<CategoryTreeVO>
     */
    private List<CategoryTreeVO> categoryListPO2VO(List<CategoryPO> categoryPO) {
        List<CategoryTreeVO> result = new ArrayList<>();
        for (CategoryPO po : categoryPO) {
            CategoryTreeVO categoryTreeVO = categoryPO2VO(po);
            result.add(categoryTreeVO);
        }
        return result;
    }


    /**
     * list转换为map 按照parentId分组
     *  1 同一个parentId的类别要放到一个list作为map的value
     *   id name,parentId
     *   9   红茶  0
     *   10  冰红茶 9
     *   11  特凉冰红茶 10
     *   12  特冰冰红茶 10
     */
    private Map<Long,List<CategoryPO>> groupPOSByParentId(List<CategoryPO> categoryPOS){
        //Map<Long,List<CategoryPO>> result = new HashMap<>();
        //方式1
      /*  for (CategoryPO categoryPO : categoryPOS) {
            Long parentId = categoryPO.getParentId();
            if (result.containsKey(parentId)) {
                result.get(parentId).add(categoryPO);
            }else {
                List<CategoryPO> list = new ArrayList<>();
                list.add(categoryPO);
                result.put(parentId,list);
            }
        }
        return result;*/

        //System.out.println(categoryPOS);
        //方式3
        // 1:A 2:C 3:D
        // 1:A 1:B 2:C 3:D   舍弃的策略 (v1,v2)->v2
        //Map<Long, CategoryPO> collect = categoryPOS.stream().collect(Collectors.toMap(CategoryPO::getParentId, Function.identity(),(v1,v2)->v2));
       // System.out.println("toMap" + collect);
        //按照parentId分组
        //System.out.println("groupingBy" + collect1);
        return categoryPOS.stream().collect(Collectors.groupingBy(CategoryPO::getParentId));
        //return result;
    }


    /**
     * 保存类别
     * @param categoryAddParam CategoryAddParam
     */
    private CategoryPO saveCategory(CategoryAddParam categoryAddParam,Integer depth) {
        CategoryPO categoryPO = new CategoryPO();
        BeanUtils.copyProperties(categoryAddParam,categoryPO);
        categoryPO.setDepth(depth);
        Integer rows = categoryRepository.save(categoryPO);
        if (rows != 1 ){
            String message = "保存类别失败,请重试.";
            log.error(message);
            throw new ServiceException(ServiceCode.ERROR_UNKNOWN,message);
        }
        return categoryPO;
    }
}
